package cn.lg.soar.system.biz.modules.auth.service.impl;

import cn.lg.soar.common.exceptions.BaseAException;
import cn.lg.soar.common.util.DatetimeUtil;
import cn.lg.soar.common.util.current.UserContext;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.listener.SpringUtils;
import cn.lg.soar.common.util.reflect.ReflectUtil;
import cn.lg.soar.common.util.reflect.SoarBeanUtils;
import cn.lg.soar.database.base.service.impl.BaseServiceImpl;
import cn.lg.soar.database.enums.ProvideTypeEnum;
import cn.lg.soar.database.enums.SystemValueEnum;
import cn.lg.soar.system.biz.modules.auth.entity.DataRuleProvide;
import cn.lg.soar.system.biz.modules.auth.mapper.DataRuleProvideMapper;
import cn.lg.soar.system.biz.modules.auth.service.IDataRuleProvideService;
import cn.lg.soar.system.biz.modules.auth.service.IUserService;
import cn.lg.soar.system.biz.modules.auth.service.UserOrgService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
 * DataRuleValue服务层实现
 * 数据源
 * @author luguoxiang
 * @since 2022-04-30
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class DataRuleProvideServiceImpl extends BaseServiceImpl<DataRuleProvideMapper, DataRuleProvide> implements IDataRuleProvideService {


    private final static DateTimeFormatter SECONDS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
    private final static DateTimeFormatter MINUTES_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm");
    private final static DateTimeFormatter HOURS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd hh");
    private final static DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private final static DateTimeFormatter MONTH_FMT = DateTimeFormatter.ofPattern("yyyy-MM");
    private final static DateTimeFormatter YEAR_FMT = DateTimeFormatter.ofPattern("yyyy");
    private final static Map<String, Supplier<Object>> SYSTEM_MAPPER = new HashMap<>();
    private final static Map<String, Supplier<Object>> PROVIDER_CACHE = new ConcurrentReferenceHashMap<>();
    private static final Cache<String, LocalDateTime> TIME_CACHE = Caffeine.newBuilder()
            .initialCapacity(32)
            .maximumSize(512)
            .expireAfterWrite(Duration.ofHours(1))
            .build();
    private static final Cache<String, LocalDate> DATE_CACHE = Caffeine.newBuilder()
            .initialCapacity(32)
            .maximumSize(512)
            .expireAfterWrite(Duration.ofHours(1))
            .build();


    public DataRuleProvideServiceImpl(UserOrgService userOrgService, IUserService userService) {
        SYSTEM_MAPPER.put(SystemValueEnum.userId.name(), UserContext::getUserId);
        SYSTEM_MAPPER.put(SystemValueEnum.username.name(), () -> userService.getById(UserContext.getUserId()).getUsername());
        SYSTEM_MAPPER.put(SystemValueEnum.usertype.name(), () -> UserContext.getUser().getUserType());
        SYSTEM_MAPPER.put(SystemValueEnum.allRoleIds.name(), () -> userService.getUserCurrentAllRoleIds(UserContext.getUserId()));
        SYSTEM_MAPPER.put(SystemValueEnum.orgIds.name(), () -> userOrgService.getOrgIds(UserContext.getUserId()));
        SYSTEM_MAPPER.put(SystemValueEnum.allOrgIds.name(), () -> userService.getUserCurrentAllOrgIds(UserContext.getUserId()));
        SYSTEM_MAPPER.put(SystemValueEnum.postIds.name(), () -> userOrgService.getPostIds(UserContext.getUserId()));
        SYSTEM_MAPPER.put(SystemValueEnum.week.name(), () -> LocalDate.now().getDayOfWeek().getValue());
        SYSTEM_MAPPER.put(SystemValueEnum.timestamp.name(), System::currentTimeMillis);
        SYSTEM_MAPPER.put(SystemValueEnum.secondsStr.name(), () -> LocalDateTime.now().format(SECONDS_FMT));
        SYSTEM_MAPPER.put(SystemValueEnum.minutesStr.name(), () -> LocalDateTime.now().format(MINUTES_FMT));
        SYSTEM_MAPPER.put(SystemValueEnum.hoursStr.name(), () -> LocalDateTime.now().format(HOURS_FMT));
        SYSTEM_MAPPER.put(SystemValueEnum.dateStr.name(), () -> LocalDate.now().format(DATE_FMT));
        SYSTEM_MAPPER.put(SystemValueEnum.monthStr.name(), () -> LocalDate.now().format(MONTH_FMT));
        SYSTEM_MAPPER.put(SystemValueEnum.yearStr.name(), () -> LocalDate.now().format(YEAR_FMT));
        SYSTEM_MAPPER.put(SystemValueEnum.seconds.name(), () -> LocalDateTime.now().withNano(0));
        SYSTEM_MAPPER.put(SystemValueEnum.minutes.name(), () -> LocalDateTime.now().withNano(0).withSecond(0));
        SYSTEM_MAPPER.put(SystemValueEnum.hours.name(), () -> LocalDateTime.now().withNano(0).withSecond(0).withMinute(0));
        SYSTEM_MAPPER.put(SystemValueEnum.date.name(), LocalDate::now);
        SYSTEM_MAPPER.put(SystemValueEnum.month.name(), () -> LocalDate.now().withDayOfMonth(1));
        SYSTEM_MAPPER.put(SystemValueEnum.year.name(), () -> LocalDate.now().withDayOfYear(1));
    }


    @Override
    public Object getColumnValue(ProvideTypeEnum provide, String columnValue) {
        switch (provide) {
            case PROVIDER:
                return PROVIDER_CACHE.computeIfAbsent(columnValue, id -> {
                    DataRuleProvide dataRuleProvide = getById(id);
                    Class<?> aClass;
                    Object bean;
                    try {
                        aClass = Class.forName(dataRuleProvide.getClassName());
                        bean = SoarBeanUtils.getBean(aClass);
                    } catch (ClassNotFoundException e) {
                        // 按bean名称获取
                        try {
                            bean = SpringUtils.getBean(dataRuleProvide.getClassName());
                        } catch (RuntimeException ignore) {
                            throw new RuntimeException(e);
                        }
                    }
                    String methodName = dataRuleProvide.getMethodName();
                    String params = dataRuleProvide.getParams();
                    Object finalBean = bean;
                    if (DataUtil.isEmpty(params)) {
                        // 无参函数
                        return () -> ReflectUtil.invoke(finalBean, methodName);
                    }
                    // 有参函数
                    return () -> ReflectUtil.invoke(finalBean, methodName, params);
                }).get();
            case SYSTEM: return SYSTEM_MAPPER.get(columnValue).get();
            case INPUT_NUMBER: return new BigDecimal(columnValue);
            case INPUT_BOOL: return Boolean.valueOf(columnValue);
            case INPUT_TIME: return TIME_CACHE.get(columnValue, DatetimeUtil::from);
            case INPUT_DATE: return DATE_CACHE.get(columnValue, DatetimeUtil::toLocalDate);
            default: return columnValue;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean save(DataRuleProvide entity) {
        check(entity);
        return super.save(entity);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateById(DataRuleProvide entity) {
        check(entity);
        boolean b = super.updateById(entity);
        if (b) {
            PROVIDER_CACHE.remove(entity.getId().toString());
        }
        return b;
    }

    /**
     * 检查
     * @param provide
     */
    public Object check(DataRuleProvide provide) {
        Class<?> aClass;
        Object bean;
        try {
            aClass = Class.forName(provide.getClassName());
            bean = SoarBeanUtils.getBean(aClass);
        } catch (ClassNotFoundException e) {
            // 按bean名称获取
            try {
                bean = SpringUtils.getBean(provide.getClassName());
            } catch (RuntimeException ignore) {
                throw new BaseAException(404, "无法找到名为 {0} 类或bean", provide.getClassName());
            }
        }
        String methodName = provide.getMethodName();
        String params = provide.getParams();
        if (DataUtil.isEmpty(params)) {
            // 无参函数
            try {
                return ReflectUtil.invoke(bean, methodName);
            } catch (RuntimeException ignore) {
                throw new BaseAException(405, "调用类 {0} 的 {1} 方法失败", provide.getClassName(), provide.getMethodName());
            }
        }
        // 有参函数
        try {
            return ReflectUtil.invoke(bean, methodName, params);
        } catch (RuntimeException ignore) {
            throw new BaseAException(405, "调用类 {0} 的 {1} 方法失败", provide.getClassName(), provide.getMethodName());
        }
    }

}
